/*
 * Copyright 2009 XueSong Guo.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package cn.webwheel.di.engine;

import cn.webwheel.compiler.JavaC;
import cn.webwheel.di.Container;
import cn.webwheel.di.DIException;
import cn.webwheel.di.Key;
import cn.webwheel.el.GenericClass;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

class CompileTypeNativeProviderGen<T> extends ReflectTypeNativeProvider<T> {

    public CompileTypeNativeProviderGen(DefaultContainer container, Key<T> key) {
        super(container, key);
        check(key);
        if (Modifier.isAbstract(key.toClass().getModifiers())) {
            throw new DIException("can not create " + key.toClass() + ". should be public class", key);
        }
        for (Context ctx : constructor.ctxs) {
            check(ctx.key);
        }
        for (MemberHelper<Field> field : fields) {
            check(field.ctxs[0].key);
        }
        for (MemberHelper<Method> method : methods) {
            for (Context ctx : method.ctxs) {
                check(ctx.key);
            }
        }
    }

    private void check(Key key) {
        if(key.toClass().isPrimitive()) return;
        int m = key.toClass().getModifiers();
        if (!Modifier.isPublic(m) || (key.toClass().isLocalClass() && !Modifier.isStatic(m))) {
            throw new DIException(key.toClass() + " should be public type", key);
        }
    }

    private String getInst(Context ctx, int idx) {
        String s = "";
        if (JavaC.javassist) {
            if (!ctx.isFetcher) {
                s = "(" + ctx.key.toClass().getCanonicalName() + ")";
                if (ctx.key.toClass().isPrimitive()) {
                    GenericClass w = GenericClass.wrap(new GenericClass(ctx.key.toClass()));
                    return s + GenericClass.toPrimitive(w.toClass(), "((" + w + ")container.getInstance(k" + idx + ", \"" + ctx.data.replace("\"", "\\\"") + "\"))");
                }
            }
        }
        return s + "container." + (ctx.isFetcher ? "createFetcher" : "getInstance") + "(k" + idx + ", \"" + ctx.data.replace("\"", "\\\"") + "\")";
    }

    @SuppressWarnings("unchecked")
    public TypeNativeProvider<T> gen() {
        GenericClass type = new GenericClass(key.getType());
        String name = "DI_" + key.toClass().getSimpleName();
        StringBuilder sb = new StringBuilder("import " + Container.class.getPackage().getName() + ".*;");
        sb.append("import " + TypeNativeProvider.class.getPackage().getName() + ".*;");
        if (JavaC.javassist) {
            sb.append("public class " + name + " implements " + TypeNativeProvider.class.getName() + " {");
        } else {
            sb.append("public class " + name + " implements " + TypeNativeProvider.class.getName() + "<" + type + "> {");
        }
        sb.append("public Container container;");
        sb.append("public Key key;");
        int idx = 0;
        for (Context ctx : constructor.ctxs) {
            declare(sb, ctx, idx++);
        }
        for (MemberHelper<Field> field : fields) {
            declare(sb, field.ctxs[0], idx++);
        }
        for (MemberHelper<Method> method : methods) {
            for (Context ctx : method.ctxs) {
                declare(sb, ctx, idx++);
            }
        }
        if (JavaC.javassist) {
            sb.append("public Object get() {");
        } else {
            sb.append("public " + type + " get() {");
        }
        sb.append("try {");
        sb.append(type + " obj = new " + type + "(");
        idx = 0;
        for (Context ctx : constructor.ctxs) {
            sb.append(getInst(ctx, idx)).append(',');
            idx++;
        }
        if(sb.charAt(sb.length()-1)=='(') sb.append(')');
        else sb.setCharAt(sb.length()-1, ')');
        sb.append(';');

        for (MemberHelper<Field> field : fields) {
            Context ctx = field.ctxs[0];
            sb.append("((" + field.member.getDeclaringClass().getName() + ")obj)." + field.member.getName() + " = " + getInst(ctx, idx) + ";");
            idx++;
        }

        for (MemberHelper<Method> method : methods) {
            sb.append("obj." + method.member.getName() + "(");
            for (Context ctx : method.ctxs) {
                sb.append(getInst(ctx, idx)).append(',');
                idx++;
            }
            if(sb.charAt(sb.length()-1)=='(') sb.append(')');
            else sb.setCharAt(sb.length()-1, ')');
            sb.append(';');
        }

        sb.append("return obj;");
        sb.append("} catch(" + DIException.class.getName() + " e) {");
        sb.append("e.add(key);");
        sb.append("throw e;");
        sb.append("} catch(Exception e) {");
        sb.append("throw new " + DIException.class.getName() + "(e.getMessage(), key, e);");
        sb.append("}");
        sb.append("}");
        sb.append("}");
        try {
            Class cls = JavaC.getInst(container.getClass().getClassLoader()).compile(name, sb.toString());
            Object obj = cls.newInstance();
            cls.getField("container").set(obj, container);
            cls.getField("key").set(obj, key);
            idx = 0;
            for (Context ctx : constructor.ctxs) {
                set(cls, obj, ctx, idx++);
            }
            for (MemberHelper<Field> field : fields) {
                set(cls, obj, field.ctxs[0], idx++);
            }
            for (MemberHelper<Method> method : methods) {
                for (Context ctx : method.ctxs) {
                    set(cls, obj, ctx, idx++);
                }
            }
            return (TypeNativeProvider<T>) obj;
        } catch (Exception e) {
            throw new DIException("impossible " + e.getMessage(), key, e);
        }
    }

    private void set(Class cls, Object obj, Context ctx, int idx) throws NoSuchFieldException, IllegalAccessException {
        cls.getField("k" + idx).set(obj, ctx.key);
    }

    private void declare(StringBuilder sb, Context ctx, int idx) {
        if (JavaC.javassist) {
            sb.append("public Key k" + idx + ";");
        } else {
            sb.append("public Key<" + GenericClass.wrap(new GenericClass(ctx.key.getType())) + "> k" + idx + ";");
        }
    }
}
